/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: PrismiqMediaRenderer.java,v $
 *
 */

package com.cidero.bridge.prismiq;

import java.net.URL;
import java.util.Properties;
import java.util.logging.Logger;

import org.cybergarage.upnp.device.InvalidDescriptionException;

import com.cidero.bridge.MediaRendererException;
import com.cidero.bridge.MediaRendererBridge;
import com.cidero.upnp.AVTransport;
import com.cidero.upnp.ConnectionManager;
import com.cidero.upnp.RenderingControl;
import com.cidero.util.AsyncCommand;
import com.cidero.util.MrUtil;
import com.cidero.util.NetUtil;

/**
 * Prismiq implementation of the MediaRendererBridge. Prismiq commands
 * are excuted via the Prismiq remote control 'agent' running on TCP 
 * port 2253 of the Prismiq box. Commands/responses are simple text strings.
 *
 * Notes: Currently only supporting streaming audio - looks like RTSP support
 * is needed to implement PAUSE and things like that. 
 */
public class PrismiqMediaRenderer extends MediaRendererBridge
{
  private static Logger logger = Logger.getLogger("com.cidero.bridge.prismiq");

  private final static String DESCRIPTION_FILE_NAME = 
     "com/cidero/bridge/prismiq/description/MediaRenderer.xml";

  private final static int PRISMIQ_AGENT_DEFAULT_PORT = 2253;
  public final static int  DEFAULT_TIMEOUT = 5000;
  public final static int  DEFAULT_MONITOR_PERIOD = 2000;

  private static int instanceCount = 0; 

	String                 prismiqIPAddr;
  int                    port = PRISMIQ_AGENT_DEFAULT_PORT;
  int                    unitId;
  URL                    url = null;   // Current URL
  AsyncCommand           asyncCommand;
  PrismiqStateModel      prismiqStateModel;
  int                    monitorPeriodMillisec = DEFAULT_MONITOR_PERIOD;

  // Prismiq versions of UPnP services
  PrismiqRenderingControl  renderingControl;
  PrismiqAVTransport       avTransport;
  PrismiqConnectionManager connectionManager;

  /**
   * Constructor
   *
   * @param  prismiqIPAddr    IP address of Prismiq device.
   * @param  friendlyName     Friendly name (e.g. 'PrismiqLivingRoom')
   *                          This is assigned in property file
   */
  public PrismiqMediaRenderer( String prismiqIPAddr, String friendlyName )
    throws InvalidDescriptionException
  {
    super( DESCRIPTION_FILE_NAME, prismiqIPAddr, friendlyName );

    getProperties();

    //
    // Override UDN from description.xml to make it unique for friendlyName,
    // hostName combinations. Cybergarage API requires a call to setUUID,
    // followed by a call to updateUDN, to reset the UDN. 
    //
    setUUID( "Cidero-" + friendlyName + 
             "-" + NetUtil.getLocalHostName() );
    updateUDN();  // This sets the UDN to 'uuid:<UUID>'


    //
    // Status model is observed by the AVTransport and RenderingControl
    // service instances
    //
    prismiqStateModel = new PrismiqStateModel();

    // Setup Prismiq-specific versions of UPnP services
    // Note: these constructors throw a InvalidDescriptionException if 
    // the device description doesn't have a matching service
    renderingControl = new PrismiqRenderingControl( this );
    avTransport = new PrismiqAVTransport( this );
    connectionManager = new PrismiqConnectionManager( this );

    unitId = instanceCount++;

    //
    // Start separate thread to handle asynchronous communication with
    // AudioTron (using HTTP). Allow for 10 queued asynchronous commands
    //
    asyncCommand = new AsyncCommand( 10 );
    AsyncCommandThread asyncCommandThread = new AsyncCommandThread( this );
    asyncCommandThread.start();


    // Start monitoring thread
    StatusMonitorThread monitorThread = 
      new StatusMonitorThread( this, monitorPeriodMillisec );
    monitorThread.start();

  }
  
  public RenderingControl getRenderingControl() {
    return renderingControl;
  }
  public AVTransport getAVTransport() {
    return avTransport;
  }
  public ConnectionManager getConnectionManager() {
    return connectionManager;
  }

  public PrismiqStateModel getStateModel()
  {
    return prismiqStateModel;
  }

  public AsyncCommand getAsyncCommand()
  {
    return asyncCommand;
  }

  public int getPort()
  {
    return port;
  }

  /** 
   *  Get properties from property file. Not yet used, and needs to be
   *  converted to preferences mechanism if enabled (TODO)
   */
  public void getProperties()
  {
    //Properties props = MrUtil.loadProperties("PrismiqMediaRenderer.properties");

    //String tmpProp = props.getProperty("portScanRange");
    //		if( tmpProp == null )
    //    {
    //				logger.fine("No ipAddrRange property defined - using " + 
    //										ipAddrScanStart + "-" + ipAddrScanEnd );
    //		}
    //    else
    //    {
    //				String[] portRanges = tmpProp.split("-");
    //    
    //				ipAddrScanStart = Integer.parseInt( portRanges[0] );
    //				ipAddrScanEnd = Integer.parseInt( portRanges[1] );
    //
    //				logger.fine("Found ipAddrRange property start-end: " + 
    //										ipAddrScanStart + "-" + ipAddrScanEnd );
    //		}
	}

  /**
   *  Send command via the asynchronous command object. 
   *
   * @return  null if no response within timeout period
   */
  public String send( String cmd, int timeoutMillisec )
  {
    return (String)asyncCommand.send( cmd, timeoutMillisec );
  }
  public String send( String cmd )
  {
    return send( cmd, DEFAULT_TIMEOUT );
  }

  public void open() throws MediaRendererException
  {
    String response = send( "DEV_OPEN AUDIO_ONLY\n", DEFAULT_TIMEOUT );
    if( response != null )
      logger.fine("DEV_OPEN response: " + response );
    else
      throw new MediaRendererException( "open: timeout");

    // Enable async shoutcast title change msgs
    response = send( "SET ASYNCH 1 1\n", DEFAULT_TIMEOUT ); 
  }

  /**
   *  Get Prismiq status. This allows for status info (volume, mute,
   *  pause) to be passed back to control point (if status changed 
   *  due to user using the Prismiq remote)
   *
   *  Only the volume state of the Prismiq is currently retrieved 
   */
  public synchronized String getRendererState()
  {
    String response = send( "GET AUDIO_VOLUME\n", DEFAULT_TIMEOUT );
    if( response == null )
    {
      logger.warning( "No response to status request... " );        
      return null;
    }

    prismiqStateModel.parseResponse( response );
    prismiqStateModel.notifyObservers();

    //    logger.fine( "Prismiq State: Transport: " + 
    //                 prismiqStateModel.getTransportState() + 
    //                 " Volume: " + prismiqStateModel.getVolume() );

    return response;
  }

  public void close()
  {
    String response = send( "DEV_CLOSE\n", DEFAULT_TIMEOUT );
    if( response == null )
    {
      logger.warning( "No response to DEV_CLOSE command " );        
    }
  }

  public String getProxyUrlPath()
  {
    return "/Prismiq/" + getFriendlyName();
  }

  public void avTransportSetTransportURI( String uri )
    throws MediaRendererException
  {
    avTransport.setTransportURI( uri );
  }
  public void avTransportPlay( String speed ) throws MediaRendererException
  {
    avTransport.play( speed );
  }
  public void avTransportPause() throws MediaRendererException
  {
    avTransport.pause();
  }
  public void avTransportStop() throws MediaRendererException
  {
    avTransport.stop();
  }

}
